#include "File.h"


void File::open(int file_descr, FileMode m, bool own)
{
    if (fd != -1) ::close(fd);
    fd     = file_descr;
    mode   = m;
    own_fd = own;
    pos    = 0;
    buf    = xmalloc<uchar>(File_BufSize);
    if (mode == READ) size = read(fd, buf, File_BufSize);
    else              size = -1;
}

void File::open(cchar* name, cchar* mode_)
{
    if (fd != -1) ::close(fd);
    bool    has_r = strchr(mode_, 'r') != NULL;
    bool    has_w = strchr(mode_, 'w') != NULL;
    bool    has_a = strchr(mode_, 'a') != NULL;
    bool    has_p = strchr(mode_, '+') != NULL;
    bool    has_x = strchr(mode_, 'x') != NULL;
    assert(!(has_r && has_w));
    assert(has_r || has_w || has_a);

    int mask = 0;
    if      (has_p) mask |= O_RDWR;
    else if (has_r) mask |= O_RDONLY;
    else            mask |= O_WRONLY;

    if (!has_r) mask |= O_CREAT;
    if (has_w)  mask |= O_TRUNC;
    if (has_x)  mask |= O_EXCL;

    fd = open64(name, mask, S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP|S_IROTH|S_IWOTH);

    if (fd != -1){
        mode   = has_r ? READ : WRITE;
        own_fd = true;
        pos    = 0;
        if (has_a) lseek64(fd, 0, SEEK_END);
        buf    = xmalloc<uchar>(File_BufSize);
        if (mode == READ) size = read(fd, buf, File_BufSize);
        else              size = -1;
    }
}


void File::close(void)
{
    if (fd == -1) return;
    if (mode == WRITE)
        flush();
    xfree(buf); buf = NULL;
    if (own_fd)
        ::close(fd);
    fd = -1;
}

void File::seek(int64 file_pos, int whence)
{
    if (mode == WRITE){
        flush();
        pos = 0;
        lseek64(fd, file_pos, whence);
    }else{
        if (whence == SEEK_CUR) lseek64(fd, file_pos - (size - pos), SEEK_CUR);
        else                    lseek64(fd, file_pos, whence);
        size = read(fd, buf, File_BufSize);
        pos = 0;
    }
}

int64 File::tell(void)
{
    if (mode == WRITE)
        return lseek64(fd, 0, SEEK_CUR);
    else
        return lseek64(fd, 0, SEEK_CUR) - (size - pos);
}


//=================================================================================================
// Marshaling:


void putUInt(File& out, uint64 val)
{
    if (val < 0x20000000){
        uint    v = (uint)val;
        if (v < 0x80)
            out.putChar(v);
        else{
            if (v < 0x2000)
                out.putChar(0x80 | (v >> 8)),
                out.putChar((uchar)v);
            else if (v < 0x200000)
                out.putChar(0xA0 | (v >> 16)),
                out.putChar((uchar)(v >> 8)),
                out.putChar((uchar)v);
            else
                out.putChar((v >> 24) | 0xC0),
                out.putChar((uchar)(v >> 16)),
                out.putChar((uchar)(v >> 8)),
                out.putChar((uchar)v);
        }
    }else
        out.putChar(0xE0),
        out.putChar((uchar)(val >> 56)),
        out.putChar((uchar)(val >> 48)),
        out.putChar((uchar)(val >> 40)),
        out.putChar((uchar)(val >> 32)),
        out.putChar((uchar)(val >> 24)),
        out.putChar((uchar)(val >> 16)),
        out.putChar((uchar)(val >> 8)),
        out.putChar((uchar)val);
}


#if __cplusplus >= 201703L
uint64 getUInt (File& in) noexcept(false)
#else
uint64 getUInt (File& in) throw(Exception_EOF)
#endif
{
    uint byte0, byte1, byte2, byte3, byte4, byte5, byte6, byte7;
    byte0 = in.getChar();
    if (byte0 == (uint)EOF)
        throw Exception_EOF();
    if (!(byte0 & 0x80))
        return byte0;
    else{
        switch ((byte0 & 0x60) >> 5){
        case 0:
            byte1 = in.getChar();
            return ((byte0 & 0x1F) << 8) | byte1;
        case 1:
            byte1 = in.getChar();
            byte2 = in.getChar();
            return ((byte0 & 0x1F) << 16) | (byte1 << 8) | byte2;
        case 2:
            byte1 = in.getChar();
            byte2 = in.getChar();
            byte3 = in.getChar();
            return ((byte0 & 0x1F) << 24) | (byte1 << 16) | (byte2 << 8) | byte3;
        default:
            byte0 = in.getChar();
            byte1 = in.getChar();
            byte2 = in.getChar();
            byte3 = in.getChar();
            byte4 = in.getChar();
            byte5 = in.getChar();
            byte6 = in.getChar();
            byte7 = in.getChar();
            return ((uint64)((byte0 << 24) | (byte1 << 16) | (byte2 << 8) | byte3) << 32)
                 |  (uint64)((byte4 << 24) | (byte5 << 16) | (byte6 << 8) | byte7);
        }
    }
}
